1   /*                        __    __  __  __    __  ___
2    *                       \  \  /  /    \  \  /  /  __/
3    *                        \  \/  /  /\  \  \/  /  /
4    *                         \____/__/  \__\____/__/.ɪᴏ
5    * ᶜᵒᵖʸʳᶦᵍʰᵗ ᵇʸ ᵛᵃᵛʳ ⁻ ˡᶦᶜᵉⁿˢᵉᵈ ᵘⁿᵈᵉʳ ᵗʰᵉ ᵃᵖᵃᶜʰᵉ ˡᶦᶜᵉⁿˢᵉ ᵛᵉʳˢᶦᵒⁿ ᵗʷᵒ ᵈᵒᵗ ᶻᵉʳᵒ
6    */
7   package io.vavr;
8   
9   import io.vavr.collection.*;
10  import io.vavr.control.LazyBenchmark;
11  import io.vavr.idiom.ForBenchmark;
12  import io.vavr.idiom.PatternMatchingBenchmark;
13  import io.vavr.idiom.TryBenchmark;
14  import io.vavr.idiom.TupleBenchmark;
15  import org.openjdk.jmh.annotations.Mode;
16  import org.openjdk.jmh.results.RunResult;
17  import org.openjdk.jmh.runner.Runner;
18  import org.openjdk.jmh.runner.RunnerException;
19  import org.openjdk.jmh.runner.options.ChainedOptionsBuilder;
20  import org.openjdk.jmh.runner.options.OptionsBuilder;
21  import org.openjdk.jmh.runner.options.TimeValue;
22  import org.openjdk.jmh.runner.options.VerboseMode;
23  
24  import java.util.Collection;
25  import java.util.Random;
26  import java.util.concurrent.TimeUnit;
27  
28  import static io.vavr.API.Array;
29  
30  public class JmhRunner {
31      /**
32       * Runs all the available benchmarks in precision mode.
33       * Note: it takes about 3 hours.
34       */
35      public static void main(String[] args) {
36          final Array<Class<?>> CLASSES = Array(
37                  ArrayBenchmark.class,
38                  BitSetBenchmark.class,
39                  CharSeqBenchmark.class,
40                  HashSetBenchmark.class,
41                  ListBenchmark.class,
42                  PriorityQueueBenchmark.class,
43                  VectorBenchmark.class,
44  
45                  LazyBenchmark.class,
46  
47                  ForBenchmark.class,
48                  PatternMatchingBenchmark.class,
49                  TryBenchmark.class,
50                  TupleBenchmark.class
51          );
52          runDebugWithAsserts(CLASSES);
53          runSlowNoAsserts(CLASSES);
54      }
55  
56      public enum Includes {
57          JAVA("java"),
58          FUNCTIONAL_JAVA("fjava"),
59          PCOLLECTIONS("pcollections"),
60          ECOLLECTIONS("ecollections"),
61          CLOJURE("clojure"),
62          SCALAZ("scalaz"),
63          SCALA("scala"),
64          VAVR("vavr");
65  
66          private final String name;
67  
68          Includes(String name) { this.name = name; }
69  
70          @Override
71          public String toString() { return name; }
72      }
73  
74      /** enables debugging and assertions for benchmarks and production code - the speed results will be totally unreliable */
75      public static void runDebugWithAsserts(Array<Class<?>> groups, Includes... includes) {
76          run(0, 1, 1, ForkJvm.DISABLE, VerboseMode.SILENT, Assertions.ENABLE, PrintInlining.DISABLE, groups, includes);
77          MemoryUsage.printAndReset();
78      }
79  
80      @SuppressWarnings("unused")
81      public static void runQuickNoAsserts(Array<Class<?>> groups, Includes... includes) {
82          run(5, 5, 10, ForkJvm.ENABLE, VerboseMode.NORMAL, Assertions.DISABLE, PrintInlining.DISABLE, groups, includes).print();
83      }
84  
85      @SuppressWarnings("unused")
86      public static void runNormalNoAsserts(Array<Class<?>> groups, Includes... includes) {
87          run(7, 7, 300, ForkJvm.ENABLE, VerboseMode.NORMAL, Assertions.DISABLE, PrintInlining.DISABLE, groups, includes).print();
88      }
89  
90      @SuppressWarnings("unused")
91      public static void runSlowNoAsserts(Array<Class<?>> groups, Includes... includes) {
92          run(15, 15, 400, ForkJvm.ENABLE, VerboseMode.EXTRA, Assertions.DISABLE, PrintInlining.DISABLE, groups, includes).print();
93      }
94  
95      private static BenchmarkPerformanceReporter run(int warmupIterations, int measurementIterations, int millis, ForkJvm forkJvm, VerboseMode silent, Assertions assertions, PrintInlining printInlining, Array<Class<?>> groups, Includes[] includes) {
96          final Array<String> includeNames = Array.of(includes.length == 0 ? Includes.values() : includes).map(Includes::toString);
97          final Array<String> classNames = groups.map(Class::getCanonicalName);
98          final Array<RunResult> results = run(warmupIterations, measurementIterations, millis, forkJvm, silent, assertions, printInlining, classNames, includeNames);
99          return BenchmarkPerformanceReporter.of(includeNames, classNames, results);
100     }
101 
102     private static Array<RunResult> run(int warmupIterations, int measurementIterations, int millis, ForkJvm forkJvm, VerboseMode verboseMode, Assertions assertions, PrintInlining printInlining, Array<String> classNames, Array<String> includeNames) {
103         try {
104             final ChainedOptionsBuilder builder = new OptionsBuilder()
105                     .shouldDoGC(true)
106                     .verbosity(verboseMode)
107                     .shouldFailOnError(true)
108                     .mode(Mode.Throughput)
109                     .timeUnit(TimeUnit.SECONDS)
110                     .warmupTime(TimeValue.milliseconds(millis))
111                     .warmupIterations(warmupIterations)
112                     .measurementTime(TimeValue.milliseconds(millis))
113                     .measurementIterations(measurementIterations)
114                     .forks(forkJvm.forkCount)
115                   /* We are using 4Gb and setting NewGen to 100% to avoid GC during testing.
116                      Any GC during testing will destroy the iteration (i.e. introduce unreliable noise in the measurement), which should get ignored as an outlier */
117                     .jvmArgsAppend("-XX:+UseG1GC", "-Xss100m", "-Xms4g", "-Xmx4g", "-XX:MaxGCPauseMillis=1000", "-XX:+UnlockExperimentalVMOptions", "-XX:G1NewSizePercent=100", "-XX:G1MaxNewSizePercent=100", assertions.vmArg);
118 
119             final String includePattern = includeNames.mkString("\\..*?\\b(", "|", ")_");
120             classNames.forEach(name -> builder.include(name + includePattern));
121 
122             if (printInlining == PrintInlining.ENABLE) {
123                 builder.jvmArgsAppend("-XX:+UnlockDiagnosticVMOptions", "-XX:+PrintInlining"); /* might help in deciding when the JVM is properly warmed up - or where to optimize the code */
124             }
125 
126             return Array.ofAll(new Runner(builder.build()).run());
127         } catch (RunnerException e) {
128             throw new RuntimeException(e);
129         }
130     }
131 
132     /* Options */
133     private enum ForkJvm {
134         ENABLE(1),
135         DISABLE(0);
136 
137         final int forkCount;
138 
139         ForkJvm(int forkCount) {
140             this.forkCount = forkCount;
141         }
142     }
143 
144     private enum Assertions {
145         ENABLE("-enableassertions"),
146         DISABLE("-disableassertions");
147 
148         final String vmArg;
149 
150         Assertions(String vmArg) {
151             this.vmArg = vmArg;
152         }
153     }
154 
155     private enum PrintInlining {
156         ENABLE,
157         DISABLE;
158     }
159 
160     /* Helper methods */
161 
162     public static Integer[] getRandomValues(int size, int seed) {
163         return getRandomValues(size, seed, false);
164     }
165 
166     public static Integer[] getRandomValues(int size, int seed, boolean nonNegative) {
167         return getRandomValues(size, nonNegative, new Random(seed));
168     }
169 
170     public static Integer[] getRandomValues(int size, boolean nonNegative, Random random) {
171         final Integer[] results = new Integer[size];
172         for (int i = 0; i < size; i++) {
173             final int value = random.nextInt(size) - (nonNegative ? 0 : (size / 2));
174             results[i] = value;
175         }
176         return results;
177     }
178 
179     /** Randomly mutate array positions */
180     public static <T> int[] shuffle(int[] array, Random random) {
181         for (int i = array.length; i > 1; i--) {
182             swap(array, i - 1, random.nextInt(i));
183         }
184         return array;
185     }
186 
187     static <T> void swap(int[] array, int i, int j) {
188         final int temp = array[i];
189         array[i] = array[j];
190         array[j] = temp;
191     }
192 
193     /** used for dead code elimination and correctness assertion inside the benchmarks */
194     public static int aggregate(int x, int y) {
195         return x ^ y;
196     }
197 
198     /** simplifies construction of immutable collections - with assertion and memory benchmarking */
199     public static <T extends Collection<?>, R> R create(Function1<T, R> function, T source, Function1<R, Boolean> assertion) {
200         return create(function, source, source.size(), assertion);
201     }
202 
203     @SuppressWarnings("unchecked")
204     public static <T, R> R create(Function1<T, R> function, T source, int elementCount, Function1<R, Boolean> assertion) {
205         final R result = function.apply(source);
206         assert assertion.apply(result);
207 
208         MemoryUsage.storeMemoryUsages(elementCount, result);
209 
210         return result;
211     }
212 }